// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/indexed_db/leveldb/leveldb_database.h"

#include <inttypes.h>
#include <stdint.h>

#include <cerrno>
#include <memory>
#include <utility>

#include "base/files/file.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/sys_info.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/process_memory_dump.h"
#include "build/build_config.h"
#include "content/browser/indexed_db/indexed_db_class_factory.h"
#include "content/browser/indexed_db/indexed_db_tracing.h"
#include "content/browser/indexed_db/leveldb/leveldb_comparator.h"
#include "content/browser/indexed_db/leveldb/leveldb_env.h"
#include "content/browser/indexed_db/leveldb/leveldb_iterator_impl.h"
#include "content/browser/indexed_db/leveldb/leveldb_write_batch.h"
#include "third_party/leveldatabase/env_chromium.h"
#include "third_party/leveldatabase/src/helpers/memenv/memenv.h"
#include "third_party/leveldatabase/src/include/leveldb/db.h"
#include "third_party/leveldatabase/src/include/leveldb/env.h"
#include "third_party/leveldatabase/src/include/leveldb/filter_policy.h"
#include "third_party/leveldatabase/src/include/leveldb/slice.h"

using base::StringPiece;

namespace content {

// Forcing flushes to disk at the end of a transaction guarantees that the
// data hit disk, but drastically impacts throughput when the filesystem is
// busy with background compactions. Not syncing trades off reliability for
// performance. Note that background compactions which move data from the
// log to SSTs are always done with reliable writes.
//
// Sync writes are necessary on Windows for quota calculations; POSIX
// calculates file sizes correctly even when not synced to disk.
#if defined(OS_WIN)
static const bool kSyncWrites = true;
#else
// TODO(dgrogan): Either remove the #if block or change this back to false.
// See http://crbug.com/338385.
static const bool kSyncWrites = true;
#endif

static leveldb::Slice MakeSlice(const StringPiece& s)
{
    return leveldb::Slice(s.begin(), s.size());
}

static StringPiece MakeStringPiece(const leveldb::Slice& s)
{
    return StringPiece(s.data(), s.size());
}

LevelDBDatabase::ComparatorAdapter::ComparatorAdapter(
    const LevelDBComparator* comparator)
    : comparator_(comparator)
{
}

int LevelDBDatabase::ComparatorAdapter::Compare(const leveldb::Slice& a,
    const leveldb::Slice& b) const
{
    return comparator_->Compare(MakeStringPiece(a), MakeStringPiece(b));
}

const char* LevelDBDatabase::ComparatorAdapter::Name() const
{
    return comparator_->Name();
}

// TODO(jsbell): Support the methods below in the future.
void LevelDBDatabase::ComparatorAdapter::FindShortestSeparator(
    std::string* start,
    const leveldb::Slice& limit) const { }

void LevelDBDatabase::ComparatorAdapter::FindShortSuccessor(
    std::string* key) const { }

LevelDBSnapshot::LevelDBSnapshot(LevelDBDatabase* db)
    : db_(db->db_.get())
    , snapshot_(db_->GetSnapshot())
{
}

LevelDBSnapshot::~LevelDBSnapshot() { db_->ReleaseSnapshot(snapshot_); }

LevelDBDatabase::LevelDBDatabase() { }

LevelDBDatabase::~LevelDBDatabase()
{
    base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
        this);
    // db_'s destructor uses comparator_adapter_; order of deletion is important.
    CloseDatabase();
    comparator_adapter_.reset();
    env_.reset();
}

void LevelDBDatabase::CloseDatabase()
{
    if (db_) {
        base::TimeTicks begin_time = base::TimeTicks::Now();
        db_.reset();
        UMA_HISTOGRAM_MEDIUM_TIMES("WebCore.IndexedDB.LevelDB.CloseTime",
            base::TimeTicks::Now() - begin_time);
    }
}

static leveldb::Status OpenDB(
    leveldb::Comparator* comparator,
    leveldb::Env* env,
    const base::FilePath& path,
    std::unique_ptr<leveldb::DB>* db,
    std::unique_ptr<const leveldb::FilterPolicy>* filter_policy)
{
    filter_policy->reset(leveldb::NewBloomFilterPolicy(10));
    leveldb::Options options;
    options.comparator = comparator;
    options.create_if_missing = true;
    options.paranoid_checks = true;
    options.filter_policy = filter_policy->get();
    options.reuse_logs = leveldb_env::kDefaultLogReuseOptionValue;
    options.compression = leveldb::kSnappyCompression;
    options.write_buffer_size = leveldb_env::WriteBufferSize(base::SysInfo::AmountOfTotalDiskSpace(path));

    // For info about the troubles we've run into with this parameter, see:
    // https://code.google.com/p/chromium/issues/detail?id=227313#c11
    options.max_open_files = 80;
    options.env = env;

    // ChromiumEnv assumes UTF8, converts back to FilePath before using.
    leveldb::DB* db_ptr = nullptr;
    leveldb::Status s = leveldb::DB::Open(options, path.AsUTF8Unsafe(), &db_ptr);
    db->reset(db_ptr);

    return s;
}

leveldb::Status LevelDBDatabase::Destroy(const base::FilePath& file_name)
{
    leveldb::Options options;
    options.env = LevelDBEnv::Get();
    // ChromiumEnv assumes UTF8, converts back to FilePath before using.
    return leveldb::DestroyDB(file_name.AsUTF8Unsafe(), options);
}

namespace {
    class LockImpl : public LevelDBLock {
    public:
        explicit LockImpl(leveldb::Env* env, leveldb::FileLock* lock)
            : env_(env)
            , lock_(lock)
        {
        }
        ~LockImpl() override { env_->UnlockFile(lock_); }

    private:
        leveldb::Env* env_;
        leveldb::FileLock* lock_;

        DISALLOW_COPY_AND_ASSIGN(LockImpl);
    };
} // namespace

std::unique_ptr<LevelDBLock> LevelDBDatabase::LockForTesting(
    const base::FilePath& file_name)
{
    leveldb::Env* env = LevelDBEnv::Get();
    base::FilePath lock_path = file_name.AppendASCII("LOCK");
    leveldb::FileLock* lock = NULL;
    leveldb::Status status = env->LockFile(lock_path.AsUTF8Unsafe(), &lock);
    if (!status.ok())
        return std::unique_ptr<LevelDBLock>();
    DCHECK(lock);
    return base::MakeUnique<LockImpl>(env, lock);
}

static int CheckFreeSpace(const char* const type,
    const base::FilePath& file_name)
{
    std::string name = std::string("WebCore.IndexedDB.LevelDB.Open") + type + "FreeDiskSpace";
    int64_t free_disk_space_in_k_bytes = base::SysInfo::AmountOfFreeDiskSpace(file_name) / 1024;
    if (free_disk_space_in_k_bytes < 0) {
        base::Histogram::FactoryGet(
            "WebCore.IndexedDB.LevelDB.FreeDiskSpaceFailure",
            1,
            2 /*boundary*/,
            2 /*boundary*/ + 1,
            base::HistogramBase::kUmaTargetedHistogramFlag)
            ->Add(1 /*sample*/);
        return -1;
    }
    int clamped_disk_space_k_bytes = free_disk_space_in_k_bytes > INT_MAX
        ? INT_MAX
        : free_disk_space_in_k_bytes;
    const uint64_t histogram_max = static_cast<uint64_t>(1e9);
    static_assert(histogram_max <= INT_MAX, "histogram_max too big");
    base::Histogram::FactoryGet(name,
        1,
        histogram_max,
        11 /*buckets*/,
        base::HistogramBase::kUmaTargetedHistogramFlag)
        ->Add(clamped_disk_space_k_bytes);
    return clamped_disk_space_k_bytes;
}

static void ParseAndHistogramIOErrorDetails(const std::string& histogram_name,
    const leveldb::Status& s)
{
    leveldb_env::MethodID method;
    base::File::Error error = base::File::FILE_OK;
    leveldb_env::ErrorParsingResult result = leveldb_env::ParseMethodAndError(s, &method, &error);
    if (result == leveldb_env::NONE)
        return;
    std::string method_histogram_name(histogram_name);
    method_histogram_name.append(".EnvMethod");
    base::LinearHistogram::FactoryGet(
        method_histogram_name,
        1,
        leveldb_env::kNumEntries,
        leveldb_env::kNumEntries + 1,
        base::HistogramBase::kUmaTargetedHistogramFlag)
        ->Add(method);

    std::string error_histogram_name(histogram_name);

    if (result == leveldb_env::METHOD_AND_BFE) {
        DCHECK_LT(error, 0);
        error_histogram_name.append(std::string(".BFE.") + leveldb_env::MethodIDToString(method));
        base::LinearHistogram::FactoryGet(
            error_histogram_name,
            1,
            -base::File::FILE_ERROR_MAX,
            -base::File::FILE_ERROR_MAX + 1,
            base::HistogramBase::kUmaTargetedHistogramFlag)
            ->Add(-error);
    }
}

static void ParseAndHistogramCorruptionDetails(
    const std::string& histogram_name,
    const leveldb::Status& status)
{
    int error = leveldb_env::GetCorruptionCode(status);
    DCHECK_GE(error, 0);
    std::string corruption_histogram_name(histogram_name);
    corruption_histogram_name.append(".Corruption");
    const int kNumPatterns = leveldb_env::GetNumCorruptionCodes();
    base::LinearHistogram::FactoryGet(
        corruption_histogram_name,
        1,
        kNumPatterns,
        kNumPatterns + 1,
        base::HistogramBase::kUmaTargetedHistogramFlag)
        ->Add(error);
}

static void HistogramLevelDBError(const std::string& histogram_name,
    const leveldb::Status& s)
{
    if (s.ok()) {
        NOTREACHED();
        return;
    }
    enum {
        LEVEL_DB_NOT_FOUND,
        LEVEL_DB_CORRUPTION,
        LEVEL_DB_IO_ERROR,
        LEVEL_DB_OTHER,
        LEVEL_DB_MAX_ERROR
    };
    int leveldb_error = LEVEL_DB_OTHER;
    if (s.IsNotFound())
        leveldb_error = LEVEL_DB_NOT_FOUND;
    else if (s.IsCorruption())
        leveldb_error = LEVEL_DB_CORRUPTION;
    else if (s.IsIOError())
        leveldb_error = LEVEL_DB_IO_ERROR;
    base::Histogram::FactoryGet(histogram_name,
        1,
        LEVEL_DB_MAX_ERROR,
        LEVEL_DB_MAX_ERROR + 1,
        base::HistogramBase::kUmaTargetedHistogramFlag)
        ->Add(leveldb_error);
    if (s.IsIOError())
        ParseAndHistogramIOErrorDetails(histogram_name, s);
    else
        ParseAndHistogramCorruptionDetails(histogram_name, s);
}

leveldb::Status LevelDBDatabase::Open(const base::FilePath& file_name,
    const LevelDBComparator* comparator,
    std::unique_ptr<LevelDBDatabase>* result,
    bool* is_disk_full)
{
    IDB_TRACE("LevelDBDatabase::Open");
    base::TimeTicks begin_time = base::TimeTicks::Now();

    std::unique_ptr<ComparatorAdapter> comparator_adapter(
        base::MakeUnique<ComparatorAdapter>(comparator));

    std::unique_ptr<leveldb::DB> db;
    std::unique_ptr<const leveldb::FilterPolicy> filter_policy;
    const leveldb::Status s = OpenDB(comparator_adapter.get(), LevelDBEnv::Get(),
        file_name, &db, &filter_policy);

    if (!s.ok()) {
        HistogramLevelDBError("WebCore.IndexedDB.LevelDBOpenErrors", s);
        int free_space_k_bytes = CheckFreeSpace("Failure", file_name);
        // Disks with <100k of free space almost never succeed in opening a
        // leveldb database.
        if (is_disk_full)
            *is_disk_full = free_space_k_bytes >= 0 && free_space_k_bytes < 100;

        LOG(ERROR) << "Failed to open LevelDB database from "
                   << file_name.AsUTF8Unsafe() << "," << s.ToString();
        return s;
    }

    UMA_HISTOGRAM_MEDIUM_TIMES("WebCore.IndexedDB.LevelDB.OpenTime",
        base::TimeTicks::Now() - begin_time);

    CheckFreeSpace("Success", file_name);

    (*result) = base::WrapUnique(new LevelDBDatabase());
    (*result)->db_ = std::move(db);
    (*result)->comparator_adapter_ = std::move(comparator_adapter);
    (*result)->comparator_ = comparator;
    (*result)->filter_policy_ = std::move(filter_policy);
    (*result)->file_name_for_tracing = file_name.BaseName().AsUTF8Unsafe();

    return s;
}

std::unique_ptr<LevelDBDatabase> LevelDBDatabase::OpenInMemory(
    const LevelDBComparator* comparator)
{
    std::unique_ptr<ComparatorAdapter> comparator_adapter(
        base::MakeUnique<ComparatorAdapter>(comparator));
    std::unique_ptr<leveldb::Env> in_memory_env(
        leveldb::NewMemEnv(LevelDBEnv::Get()));

    std::unique_ptr<leveldb::DB> db;
    std::unique_ptr<const leveldb::FilterPolicy> filter_policy;
    const leveldb::Status s = OpenDB(comparator_adapter.get(),
        in_memory_env.get(),
        base::FilePath(),
        &db,
        &filter_policy);

    if (!s.ok()) {
        LOG(ERROR) << "Failed to open in-memory LevelDB database: " << s.ToString();
        return std::unique_ptr<LevelDBDatabase>();
    }

    std::unique_ptr<LevelDBDatabase> result = base::WrapUnique(new LevelDBDatabase());
    result->env_ = std::move(in_memory_env);
    result->db_ = std::move(db);
    result->comparator_adapter_ = std::move(comparator_adapter);
    result->comparator_ = comparator;
    result->filter_policy_ = std::move(filter_policy);
    result->file_name_for_tracing = "in-memory-database";

    return result;
}

leveldb::Status LevelDBDatabase::Put(const StringPiece& key,
    std::string* value)
{
    base::TimeTicks begin_time = base::TimeTicks::Now();

    leveldb::WriteOptions write_options;
    write_options.sync = kSyncWrites;

    const leveldb::Status s = db_->Put(write_options, MakeSlice(key), MakeSlice(*value));
    if (!s.ok())
        LOG(ERROR) << "LevelDB put failed: " << s.ToString();
    else
        UMA_HISTOGRAM_TIMES("WebCore.IndexedDB.LevelDB.PutTime",
            base::TimeTicks::Now() - begin_time);
    return s;
}

leveldb::Status LevelDBDatabase::Remove(const StringPiece& key)
{
    leveldb::WriteOptions write_options;
    write_options.sync = kSyncWrites;

    const leveldb::Status s = db_->Delete(write_options, MakeSlice(key));
    if (!s.IsNotFound())
        LOG(ERROR) << "LevelDB remove failed: " << s.ToString();
    return s;
}

leveldb::Status LevelDBDatabase::Get(const StringPiece& key,
    std::string* value,
    bool* found,
    const LevelDBSnapshot* snapshot)
{
    *found = false;
    leveldb::ReadOptions read_options;
    read_options.verify_checksums = true; // TODO(jsbell): Disable this if the
        // performance impact is too great.
    read_options.snapshot = snapshot ? snapshot->snapshot_ : 0;

    const leveldb::Status s = db_->Get(read_options, MakeSlice(key), value);
    if (s.ok()) {
        *found = true;
        return s;
    }
    if (s.IsNotFound())
        return leveldb::Status::OK();
    HistogramLevelDBError("WebCore.IndexedDB.LevelDBReadErrors", s);
    LOG(ERROR) << "LevelDB get failed: " << s.ToString();
    return s;
}

leveldb::Status LevelDBDatabase::Write(const LevelDBWriteBatch& write_batch)
{
    base::TimeTicks begin_time = base::TimeTicks::Now();
    leveldb::WriteOptions write_options;
    write_options.sync = kSyncWrites;

    const leveldb::Status s = db_->Write(write_options, write_batch.write_batch_.get());
    if (!s.ok()) {
        HistogramLevelDBError("WebCore.IndexedDB.LevelDBWriteErrors", s);
        LOG(ERROR) << "LevelDB write failed: " << s.ToString();
    } else {
        UMA_HISTOGRAM_TIMES("WebCore.IndexedDB.LevelDB.WriteTime",
            base::TimeTicks::Now() - begin_time);
    }
    return s;
}

std::unique_ptr<LevelDBIterator> LevelDBDatabase::CreateIterator(
    const LevelDBSnapshot* snapshot)
{
    leveldb::ReadOptions read_options;
    read_options.verify_checksums = true; // TODO(jsbell): Disable this if the
        // performance impact is too great.
    read_options.snapshot = snapshot ? snapshot->snapshot_ : 0;

    std::unique_ptr<leveldb::Iterator> i(db_->NewIterator(read_options));
    return std::unique_ptr<LevelDBIterator>(
        IndexedDBClassFactory::Get()->CreateIteratorImpl(std::move(i)));
}

const LevelDBComparator* LevelDBDatabase::Comparator() const
{
    return comparator_;
}

void LevelDBDatabase::Compact(const base::StringPiece& start,
    const base::StringPiece& stop)
{
    IDB_TRACE("LevelDBDatabase::Compact");
    const leveldb::Slice start_slice = MakeSlice(start);
    const leveldb::Slice stop_slice = MakeSlice(stop);
    // NULL batch means just wait for earlier writes to be done
    db_->Write(leveldb::WriteOptions(), NULL);
    db_->CompactRange(&start_slice, &stop_slice);
}

void LevelDBDatabase::CompactAll() { db_->CompactRange(NULL, NULL); }

bool LevelDBDatabase::OnMemoryDump(
    const base::trace_event::MemoryDumpArgs& args,
    base::trace_event::ProcessMemoryDump* pmd)
{
    if (!db_)
        return false;

    std::string value;
    uint64_t size;
    bool res = db_->GetProperty("leveldb.approximate-memory-usage", &value);
    DCHECK(res);
    base::StringToUint64(value, &size);

    auto* dump = pmd->CreateAllocatorDump(base::StringPrintf(
        "leveldb/index_db/0x%" PRIXPTR, reinterpret_cast<uintptr_t>(db_.get())));
    dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
        base::trace_event::MemoryAllocatorDump::kUnitsBytes, size);
    dump->AddString("file_name", "", file_name_for_tracing);

    // Memory is allocated from system allocator (malloc).
    pmd->AddSuballocation(dump->guid(),
        base::trace_event::MemoryDumpManager::GetInstance()
            ->system_allocator_pool_name());
    return true;
}

} // namespace content
